﻿using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using ZeroSum.Domain.Entities;
using ZeroSum.Extensions;

namespace ZeroSum.Extend.EFCore.MySql.EntityFrameworks;

public class ZeroSumSaveChangesInterceptor : SaveChangesInterceptor
{
    private readonly IHttpContextAccessor _accessor;

    public ZeroSumSaveChangesInterceptor(IHttpContextAccessor accessor)
    {
        _accessor = accessor;
    }

    public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
    {
        SoftDeleteTracking(eventData.Context!);
        CreateTracking(eventData.Context!);
        UpdateTracking(eventData.Context!);
        return base.SavingChanges(eventData, result);
    }

    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData,
        InterceptionResult<int> result, CancellationToken cancellationToken = default)
    {
        SoftDeleteTracking(eventData.Context!);
        CreateTracking(eventData.Context!);
        UpdateTracking(eventData.Context!);
        return await base.SavingChangesAsync(eventData, result, cancellationToken);
    }

    private static void SoftDeleteTracking(DbContext dbContext)
    {
        var deletedEntries = dbContext.ChangeTracker.Entries()
            .Where(entry => entry.State == EntityState.Deleted && entry.Entity is ISoftDelete);
        deletedEntries?.ToList().ForEach(entityEntry =>
        {
            entityEntry.Reload();
            entityEntry.State = EntityState.Modified;
            ((ISoftDelete) entityEntry.Entity).IsDeleted = true;
        });
    }

    private void CreateTracking(DbContext dbContext)
    {
        var addEntities = dbContext.ChangeTracker.Entries().ToList()
            .Where(entry => entry.State == EntityState.Added).ToList();

        if (_accessor.HttpContext?.User?.Identity == null || !_accessor.HttpContext.User.Identity.IsAuthenticated)
            return;
        var identity = _accessor.HttpContext.User.Identity;
        var entityEntries = addEntities.Where(entry => entry.Entity is ICreated);
        entityEntries?.ToList().ForEach(entityEntry =>
        {
            ((ICreated) entityEntry.Entity).CreatedTime = DateTimeOffset.Now;
            ((ICreated) entityEntry.Entity).CreatedUserId = identity.GetUserId();
            ((ICreated) entityEntry.Entity).CreatedUserName = identity.GetUserName();
        });
    }

    private void UpdateTracking(DbContext dbContext)
    {
        if (_accessor.HttpContext == null || _accessor.HttpContext.User == null ||
            _accessor.HttpContext.User.Identity == null ||
            !_accessor.HttpContext.User.Identity.IsAuthenticated)
            return;
        var identity = _accessor.HttpContext.User.Identity;
        var deletedEntries = dbContext.ChangeTracker.Entries()
            .Where(entry => entry.State == EntityState.Deleted && entry.Entity is IUpdate);
        deletedEntries?.ToList().ForEach(entityEntry =>
        {
            ((IUpdate) entityEntry.Entity).UpdatedTime = DateTimeOffset.Now;
            ((IUpdate) entityEntry.Entity).UpdatedUserId = identity.GetUserId();
            ((IUpdate) entityEntry.Entity).UpdatedUserName = identity.GetUserName();
        });
    }
}